/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Alexei Y. Zakharov
 */

package org.apache.harmony.jndi.provider.dns;

import java.util.Enumeration;
import java.util.Vector;

import org.apache.harmony.jndi.internal.nls.Messages;

/**
 * This class represents a domain protocol message.
 * 
 * @see RFC 1035
 */
public class Message {

	// header section fields

	/**
	 * Parses given sequence of bytes and constructs a message object from it.
	 * 
	 * @param mesBytes
	 *            the byte array that should be parsed
	 * @param startIdx
	 *            an index of <code>mesBytes</code> array to start the parsing
	 *            at
	 * @param mes
	 *            an object to write a result to, should already be created
	 * @return updated index of <code>mesBytes</code> array
	 * @throws DomainProtocolException
	 *             if some error has occurred
	 */
	public static int parseMessage(byte[] mesBytes, int startIdx, Message mesObj)
			throws DomainProtocolException {
		int idx = startIdx;
		int tmp, tmp2;
		int qdCnt;
		int anCnt;
		int nsCnt;
		int arCnt;

		if (mesObj == null) {
			// jndi.58=The value of parameter mesObj is null
			throw new DomainProtocolException(Messages.getString("jndi.58")); //$NON-NLS-1$
		}
		// header section
		// ID
		mesObj.setId(ProviderMgr.parse16Int(mesBytes, idx));
		idx += 2;
		// QR & opCode & AA & TC & RD & RA & Z & rCode
		tmp = ProviderMgr.parse16Int(mesBytes, idx);
		idx += 2;
		// QR
		mesObj.setQR(ProviderMgr.checkBit(tmp, ProviderConstants.QR_MASK));
		// OPCODE
		tmp2 = (tmp & ProviderConstants.OPCODE_MASK) >> ProviderConstants.OPCODE_SHIFT;
		mesObj.setOpCode(tmp2);
		// AA
		mesObj.setAA(ProviderMgr.checkBit(tmp, ProviderConstants.AA_MASK));
		// TC
		mesObj.setTc(ProviderMgr.checkBit(tmp, ProviderConstants.TC_MASK));
		// RD
		mesObj.setRD(ProviderMgr.checkBit(tmp, ProviderConstants.RD_MASK));
		// RA
		mesObj.setRA(ProviderMgr.checkBit(tmp, ProviderConstants.RA_MASK));
		// RCODE
		tmp2 = (tmp & ProviderConstants.RCODE_MASK) >> ProviderConstants.RCODE_SHIFT;
		mesObj.setRCode(tmp2);
		// QDCOUNT
		qdCnt = ProviderMgr.parse16Int(mesBytes, idx);
		mesObj.setQDCount(qdCnt);
		idx += 2;
		// ANCOUNT
		anCnt = ProviderMgr.parse16Int(mesBytes, idx);
		mesObj.setANCount(anCnt);
		idx += 2;
		// NSCOUNT
		nsCnt = ProviderMgr.parse16Int(mesBytes, idx);
		mesObj.setNSCount(nsCnt);
		idx += 2;
		// ARCOUNT
		arCnt = ProviderMgr.parse16Int(mesBytes, idx);
		mesObj.setARCount(arCnt);
		idx += 2;
		// question section
		for (int i = 0; i < qdCnt; i++) {
			final QuestionRecord qr = new QuestionRecord();
			idx = QuestionRecord.parseRecord(mesBytes, idx, qr);
			mesObj.addQuestionRecord(qr);
		}
		// answer section
		for (int i = 0; i < anCnt; i++) {
			final ResourceRecord rr = new ResourceRecord();
			idx = ResourceRecord.parseRecord(mesBytes, idx, rr);
			mesObj.addAnswerRR(rr);
		}
		// authority section
		for (int i = 0; i < nsCnt; i++) {
			final ResourceRecord rr = new ResourceRecord();
			idx = ResourceRecord.parseRecord(mesBytes, idx, rr);
			mesObj.addAuthorityRR(rr);
		}
		// additional section
		for (int i = 0; i < arCnt; i++) {
			final ResourceRecord rr = new ResourceRecord();
			idx = ResourceRecord.parseRecord(mesBytes, idx, rr);
			mesObj.addAdditionalRR(rr);
		}
		return idx;
	}

	/** an ID */
	private int id = 0;

	/** QR; false stands for QUERY request, true for RESPONSE */
	private boolean qr = ProviderConstants.QR_QUERY;

	/** OPCODE; QUERY or IQUERY or STATUS */
	private int opCode = 0;

	/** AA, Authoritative Answer */
	private boolean aa = false;

	/** TC, TrunCation */
	private boolean tc = false;

	/** RD, Recursion Desired */
	private boolean rd = false;

	/** RA, Recursion Available */
	private boolean ra = false;

	/** Z, always should be zero */
	// private int z;
	/** RCODE, Response CODE */
	private int rCode = 0;

	/** QDCOUNT, number of records in question section */
	private int qdCount = 0;

	/** ANCOUNT, number of records in answer section */
	private int anCount = 0;

	/** NSCOUNT, number of records in authority records section */
	private int nsCount = 0;

	/** ARCOUNT, number of records in additional section */
	private int arCount = 0;

	private Vector<QuestionRecord> questionRecords = null;

	private Vector<ResourceRecord> answerRRs = null;

	private Vector<ResourceRecord> authorityRRs = null;

	private Vector<ResourceRecord> additionalRRs = null;

	/** */
	public Message() {
		questionRecords = new Vector<QuestionRecord>();
		answerRRs = new Vector<ResourceRecord>();
		authorityRRs = new Vector<ResourceRecord>();
		additionalRRs = new Vector<ResourceRecord>();
	}

	/**
	 * Constructs Message object from given parameters
	 * 
	 * @param id
	 *            ID
	 * @param qr
	 *            QR
	 * @param opCode
	 *            OPCODE
	 * @param aa
	 *            AA
	 * @param tc
	 *            TC
	 * @param rd
	 *            RA
	 * @param ra
	 *            RA
	 * @param rCode
	 *            RCODE
	 * @param qdCount
	 *            QDCOUNT
	 * @param anCount
	 *            ANCOUNT
	 * @param nsCount
	 *            NSCOUNT
	 * @param arCount
	 *            ARCOUNT
	 */
	public Message(int id, boolean qr, int opCode, boolean aa, boolean tc,
			boolean rd, boolean ra, int rCode, int qdCount, int anCount,
			int nsCount, int arCount) {
		this.id = id;
		this.qr = qr;
		this.opCode = opCode;
		this.aa = aa;
		this.tc = tc;
		this.rd = rd;
		this.ra = ra;
		this.rCode = rCode;
		this.qdCount = qdCount;
		this.anCount = anCount;
		this.nsCount = nsCount;
		this.arCount = arCount;
		questionRecords = new Vector<QuestionRecord>();
		answerRRs = new Vector<ResourceRecord>();
		authorityRRs = new Vector<ResourceRecord>();
		additionalRRs = new Vector<ResourceRecord>();
	}

	/**
	 * Adds a new question record to the message.
	 * 
	 * @param rr
	 *            a record to add
	 */
	public void addAdditionalRR(ResourceRecord rr) {
		additionalRRs.addElement(rr);
	}

	/**
	 * Adds a new question record to the message.
	 * 
	 * @param rr
	 *            a record to add
	 */
	public void addAnswerRR(ResourceRecord rr) {
		answerRRs.addElement(rr);
	}

	/**
	 * Adds a new question record to the message.
	 * 
	 * @param rr
	 *            a record to add
	 */
	public void addAuthorityRR(ResourceRecord rr) {
		authorityRRs.addElement(rr);
	}

	/**
	 * Adds a new question record to the message.
	 * 
	 * @param qr
	 *            a record to add
	 */
	public void addQuestionRecord(QuestionRecord qr) {
		questionRecords.addElement(qr);
	}

	/**
	 * @return available additional resource records
	 */
	public Enumeration<ResourceRecord> getAdditionalRRs() {
		return additionalRRs.elements();
	}

	/**
	 * @return Returns the anCount.
	 */
	public int getANCount() {
		return anCount;
	}

	/**
	 * @return available answer resource records
	 */
	public Enumeration<ResourceRecord> getAnswerRRs() {
		return answerRRs.elements();
	}

	/**
	 * @return Returns the arCount.
	 */
	public int getARCount() {
		return arCount;
	}

	/**
	 * @return available authority resource records
	 */
	public Enumeration<ResourceRecord> getAuthorityRRs() {
		return authorityRRs.elements();
	}

	/**
	 * @return Returns the id.
	 */
	public int getId() {
		return id;
	}

	/**
	 * @return Returns the nsCount.
	 */
	public int getNSCount() {
		return nsCount;
	}

	/**
	 * @return Returns the opCode.
	 */
	public int getOpCode() {
		return opCode;
	}

	/**
	 * @return Returns the qdCount.
	 */
	public int getQDCount() {
		return qdCount;
	}

	/**
	 * @return Returns the QR.
	 */
	public boolean getQR() {
		return qr;
	}

	/**
	 * @return question records that are contained by the current message.
	 */
	public Enumeration<QuestionRecord> getQuestionRecords() {
		return questionRecords.elements();
	}

	/**
	 * @return Returns the rCode.
	 */
	public int getRCode() {
		return rCode;
	}

	/**
	 * @return Returns the AA.
	 */
	public boolean isAA() {
		return aa;
	}

	/**
	 * @return Returns the RA.
	 */
	public boolean isRA() {
		return ra;
	}

	/**
	 * @return Returns the RD.
	 */
	public boolean isRD() {
		return rd;
	}

	/**
	 * @return Returns the TC.
	 */
	public boolean isTc() {
		return tc;
	}

	/**
	 * @param aa
	 *            The AA to set.
	 */
	public void setAA(boolean aa) {
		this.aa = aa;
	}

	/**
	 * @param anCount
	 *            The anCount to set.
	 */
	public void setANCount(int anCount) {
		this.anCount = anCount;
	}

	/**
	 * @param arCount
	 *            The arCount to set.
	 */
	public void setARCount(int arCount) {
		this.arCount = arCount;
	}

	/**
	 * @param id
	 *            The id to set.
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * @param nsCount
	 *            The nsCount to set.
	 */
	public void setNSCount(int nsCount) {
		this.nsCount = nsCount;
	}

	/**
	 * @param opCode
	 *            The opCode to set.
	 */
	public void setOpCode(int opCode) {
		this.opCode = opCode;
	}

	/**
	 * @param qdCount
	 *            The qdCount to set.
	 */
	public void setQDCount(int qdCount) {
		this.qdCount = qdCount;
	}

	/**
	 * @param qr
	 *            The QR to set.
	 */
	public void setQR(boolean qr) {
		this.qr = qr;
	}

	/**
	 * @param ra
	 *            The RA to set.
	 */
	public void setRA(boolean ra) {
		this.ra = ra;
	}

	/**
	 * @param code
	 *            The rCode to set.
	 */
	public void setRCode(int code) {
		rCode = code;
	}

	/**
	 * @param rd
	 *            The RD to set.
	 */
	public void setRD(boolean rd) {
		this.rd = rd;
	}

	/**
	 * @param tc
	 *            The TC to set.
	 */
	public void setTc(boolean tc) {
		this.tc = tc;
	}

	/**
	 * @return string representation of this message
	 */
	@Override
	@SuppressWarnings("nls")
	public String toString() {
		final StringBuilder sb = new StringBuilder();

		sb.append("ID=" + id + "\n");
		if (qr) {
			sb.append(" QR");
		}
		sb.append(" OPCODE=" + opCode);
		if (aa) {
			sb.append(" AA");
		}
		if (tc) {
			sb.append(" TC");
		}
		if (rd) {
			sb.append(" RD");
		}
		if (ra) {
			sb.append(" RA");
		}
		sb.append(" RCODE=" + rCode);
		sb.append("\n");
		sb.append("QDCOUNT=" + qdCount);
		for (int i = 0; i < questionRecords.size(); i++) {
			sb.append("\n");
			sb.append(questionRecords.elementAt(i).toString());
		}
		sb.append("\n");
		sb.append(" ANCOUNT=" + anCount);
		for (int i = 0; i < answerRRs.size(); i++) {
			sb.append("\n");
			sb.append(answerRRs.elementAt(i).toString());
		}
		sb.append("\n");
		sb.append(" NSCOUNT=" + nsCount);
		for (int i = 0; i < authorityRRs.size(); i++) {
			sb.append("\n");
			sb.append(authorityRRs.elementAt(i).toString());
		}
		sb.append("\n");
		sb.append(" ARCOUNT=" + arCount);
		for (int i = 0; i < additionalRRs.size(); i++) {
			sb.append("\n");
			sb.append(additionalRRs.elementAt(i).toString());
		}
		return sb.toString();
	}

	/**
	 * Generates sequence of bytes that represents the message.
	 * 
	 * @param buffer
	 *            the buffer to write bytes into
	 * @param startIdx
	 *            the index of <code>buffer</code> to start writing at
	 * @return updated index of the <code>buffer</code>
	 * @throws DomainProtocolException
	 *             if something went wrong
	 */
	public int writeBytes(byte[] buffer, int startIdx)
			throws DomainProtocolException {
		int idx = startIdx;
		int tmp = 0;

		// basic check
		if (buffer == null) {
			// jndi.32=buffer is null
			throw new DomainProtocolException(Messages.getString("jndi.32")); //$NON-NLS-1$
		}
		// ID
		idx = ProviderMgr.write16Int(id, buffer, idx);
		// QR
		tmp = ProviderMgr.setBit(tmp, ProviderConstants.QR_MASK, qr);
		// OPCODE
		tmp &= ~ProviderConstants.OPCODE_MASK;
		tmp |= (opCode & 0xf) << ProviderConstants.OPCODE_SHIFT;
		// AA
		tmp = ProviderMgr.setBit(tmp, ProviderConstants.AA_MASK, aa);
		// TC
		tmp = ProviderMgr.setBit(tmp, ProviderConstants.TC_MASK, tc);
		// RD
		tmp = ProviderMgr.setBit(tmp, ProviderConstants.RD_MASK, rd);
		// RA
		tmp = ProviderMgr.setBit(tmp, ProviderConstants.RA_MASK, ra);
		// Z, drop all those bits
		tmp &= ~ProviderConstants.Z_MASK;
		// RCODE
		tmp &= ~ProviderConstants.RCODE_MASK;
		tmp |= (rCode & 0xf) << ProviderConstants.RCODE_SHIFT;
		// write to buffer
		idx = ProviderMgr.write16Int(tmp, buffer, idx);
		// QDCOUNT
		idx = ProviderMgr.write16Int(qdCount, buffer, idx);
		// ANCOUNT
		idx = ProviderMgr.write16Int(anCount, buffer, idx);
		// NSCOUNT
		idx = ProviderMgr.write16Int(nsCount, buffer, idx);
		// ARCOUNT
		idx = ProviderMgr.write16Int(arCount, buffer, idx);
		// question section
		for (int i = 0; i < questionRecords.size(); i++) {
			final QuestionRecord qr = questionRecords.elementAt(i);

			idx = qr.writeBytes(buffer, idx);
		}
		// answer section
		for (int i = 0; i < answerRRs.size(); i++) {
			final ResourceRecord rr = answerRRs.elementAt(i);

			idx = rr.writeBytes(buffer, idx);
		}
		// authority section
		for (int i = 0; i < authorityRRs.size(); i++) {
			final ResourceRecord rr = answerRRs.elementAt(i);

			idx = rr.writeBytes(buffer, idx);
		}
		// additional section
		for (int i = 0; i < additionalRRs.size(); i++) {
			final ResourceRecord rr = answerRRs.elementAt(i);

			idx = rr.writeBytes(buffer, idx);
		}
		return idx;
	}

}
